package com.zhuzi.utils;

import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.zhuzi.fun.FunCall;
import com.zhuzi.fun.RetryerFunCall;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalTime;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * 重试组件
 * <a href="https://blog.csdn.net/aitangyong/article/details/53889036">等待时间策略</a>
 *
 * @author zhuzi
 * <p>
 * spring-retry 和 guava-retry 工具都是线程安全的重试，能够支持并发业务场景的重试逻辑正确性。两者都很好的将正常方法和重试方法进行了解耦，可以设置超时时间、重试次数、间隔时间、监听结果、都是不错的框架。
 * <p>
 * 但是明显感觉得到，guava-retry在使用上更便捷，更灵活，能根据方法返回值来判断是否重试，而Spring-retry只能根据抛出的异常来进行重试。
 */
@Slf4j
public class RetryerUtils {

    static int err = 0;

    public static Exception retry(RetryerFunCall call) {
        Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                //设置异常重试源
                .retryIfExceptionOfType(Exception.class)
                //设置等待间隔时间
                .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
                //设置最大重试次数
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        try {
            retryer.call(() -> {
                call.exec();
                return true;
            });
        } catch (Exception e) {
            log.error("重试组件失败", e);
            return e;
        }
        return null;
    }

    /**
     * 重试调用
     *
     * @param call    调用
     * @param funCall 回调
     * @param msg     回调参数
     */
    public static void retryCall(RetryerFunCall call, FunCall funCall, String msg) {
        Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                //设置异常重试源
                .retryIfExceptionOfType(Exception.class)
                //设置等待间隔时间
                .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
                //设置最大重试次数
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        try {
            retryer.call(() -> {
                call.exec();
                return true;
            });
        } catch (Exception e) {
            log.error("重试组件失败", e);
            try {
                funCall.call(e, msg);
            } catch (Exception ex) {
                log.error("retryCall", ex);
            }
        }

    }

    public static <V> V retryCallFun(Callable<V> call) {
        Retryer<V> retryer = RetryerBuilder.<V>newBuilder()
                //设置异常重试源
                .retryIfExceptionOfType(Exception.class)
                //设置等待间隔时间
                .withWaitStrategy(WaitStrategies.randomWait(3, TimeUnit.SECONDS, 5, TimeUnit.SECONDS))
                //设置最大重试次数
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        try {
            return retryer.call(call);
        } catch (Exception e) {
            log.error("重试组件失败", e);
        }
        return null;
    }

    // 需要重试执行的操作
    private static Boolean testCode() {
        System.out.println(LocalTime.now());

        // 强制抛出异常，触发重试
        err++;
        if (err < 8) {
            throw new RuntimeException("手动测试抛出异常");
        } else {
            return false;
        }
    }

    public static void main(String[] args) throws Exception {
        //retry(() -> testCode());
        //将lambda替换为引用如下
        //retry(RetryerUtils::testCode);

        FunCall<Exception, String> funCall = ((e, s) -> {
            log.info("我被回调了[{}]", s);
        });
        //retryCall(() -> testCode(), funCall, "哈哈😄🐶");
        Boolean aBoolean = retryCallFun(() -> testCode());
        System.out.println(aBoolean);
    }
}
